/********************************************************************//**
* @file		EventManager.hpp
* @brief	Manage events
* 
************************************************************************/
#ifndef __EVENTMANAGER_HPP__
#define __EVENTMANAGER_HPP__

#include <IEventReceiver.h>

#include <map>
#include <list>

#include <boost/signals2.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/thread/mutex.hpp>

#include "EventTypes.hpp"


#define REMOTE_CONNECT(_typeEvent, _functionPointer, _objectPtr)                    Event::EventManager::getSingletonPtr()->connect(_objectPtr, _typeEvent, boost::bind(_functionPointer, _objectPtr, _1))
#define CONNECT(_typeEvent, _functionPointer)                                       Event::EventManager::getSingletonPtr()->connect(this, _typeEvent, boost::bind(_functionPointer, this, _1))
#define CONNECT_SENDER(_typeEvent, _sender, _functionPointer)                       Event::EventManager::getSingletonPtr()->connect(this, _typeEvent, _sender, boost::bind(_functionPointer, this, _1))
#define POST_EVENT(_event)                                                          Event::EventManager::getSingletonPtr()->postEvent(_event)
#define GET_REAL_EVENT_TYPE( _typeEvent, _eventVariableDest, _eventVariableSrc )    boost::shared_ptr<_typeEvent> _eventVariableDest = boost::dynamic_pointer_cast<_typeEvent>( _eventVariableSrc )

namespace irr
{
    struct SEvent;
}

namespace Event
{
    class EventBase;

    /**
    * @class        EventManager
    * @brief        Singleton class to manage events
    */
    class EventManager :
        public irr::IEventReceiver
    {
        static EventManager* m_Singleton; /// Unique instance of the singleton

    public:
        typedef boost::signals2::signal<void (boost::shared_ptr<EventBase>) >           td_eventBaseSignal; /// Typedef for a signal
        typedef std::map< unsigned int, td_eventBaseSignal * >                          td_mapSignals; /// Typedef for the map of signals
        typedef std::map< std::pair< unsigned int, void* >, td_eventBaseSignal * >      td_mapSenderSignals; /// Typedef for the map of signals with sender
        typedef std::multimap< unsigned int, boost::signals2::shared_connection_block > td_mapConnectionBlock; /// Typedef for the map of connections blocker and its eevent ID
        typedef std::map< void*, td_mapConnectionBlock >                                td_mapListeners; /// Typedef for the map of connections blockerwith its listener
        typedef std::list< boost::shared_ptr<EventBase> >                               td_listEvents; /// Typedef for the list of events

    private:
        /**
        * @fn       EventManager()
        * @brief    Constructor
        */
        EventManager();

        /**
        * @fn       ~EventManager()
        * @brief    Destructor
        */
        ~EventManager();

        boost::mutex        m_listMutex;
        td_mapSignals       m_mapSignals; ///Map of signals
        td_mapSenderSignals m_mapSenderSignals; ///Map of signals with sender
        td_mapListeners     m_mapListeners; ///Map of listeners
        td_listEvents       m_listEvents; ///List of events

    public:

        /**
        * @fn		getSingletonPtr()
        * @brief	Getter on the singleton
        * @return	The unique instance of the singleton
        */
        static EventManager* getSingletonPtr()
        {
            if (m_Singleton == NULL)
                m_Singleton = new EventManager();
            return m_Singleton;
        }

        /**
        * @fn		getSingleton()
        * @brief	Getter on the singleton
        * @return	The unique instance of the singleton
        */
        static EventManager& getSingleton()
        {
            if (m_Singleton == NULL)
                m_Singleton = new EventManager();
            return *m_Singleton;
        }

        /**
        * @fn		createSingleton()
        * @brief	Create the singleton
        * @return	The unique instance of the singleton
        */
        static EventManager* createSingleton()
        {
            if (m_Singleton == NULL)
                m_Singleton = new EventManager();
            return m_Singleton;
        }

        /**
        * @fn		destroySingleton()
        * @brief	Destroy the singleton
        */
        static void destroySingleton()
        {
            if( m_Singleton != NULL )
                delete m_Singleton;
            m_Singleton = NULL;
        }

        // This is the one method that we have to implement
        virtual bool OnEvent(const irr::SEvent& _event);

        /**
        * @fn       update(float _timeDelta)
        * @brief    update the manager and fire the received events
        * @param   _timeDelta  the time delta
        */
        void update(float _timeDelta);

        /**
        * @fn       connect(void* _listener, enum_typeEvent _eventID, const td_eventBaseSignal::slot_type &_subscriber)
        * @brief    Connect a subscriber to an event defined by _eventID
        * @param   _listener           listener of the event
        * @param    _eventID        the event we want to listen
        * @param   _subscriber         function listening to the event
        */
        void connect(void* _listener, enum_typeEvent _eventID, const td_eventBaseSignal::slot_type &_subscriber);

        /**
        * @fn       connect(void* _listener, enum_typeEvent _eventID, void * _sender, const td_eventBaseSignal::slot_type &_subscriber)
        * @brief    Connect a subscriber to an event defined by _eventID and a sender
        * @param   _listener           listener of the event
        * @param    _eventID        the event we want to listen
        * @param   _sender           sender of the event
        * @param   _subscriber         function listening to the event
        */
        void connect(void* _listener, enum_typeEvent _eventID, void * _sender, const td_eventBaseSignal::slot_type &_subscriber);

        /**
        * @fn       postEvent(boost::shared_ptr<EventBase> _event)
        * @brief    add an incoming event to the list
        * @param    _event          the event we want to post
        */
        void postEvent(boost::shared_ptr<EventBase> _event);

        /**
        * @fn       compareEvents(boost::shared_ptr<EventBase> first, boost::shared_ptr<EventBase> second)
        * @brief    Compare two shared_ptr on EventBase
        * @param    _first      First event to compare
        * @param    _second     Second event to compare
        * @return   True if the first event is lower than the second
        */
        static bool compareEvents(boost::shared_ptr<EventBase> _first, boost::shared_ptr<EventBase> _second);

        /**
        * @fn       blockConnection(T * _listener, enum_typeEvent _eventID)
        * @brief    Block a connection for a listener and its event type
        * @param    _listener      the listener of events
        * @param    _eventID       the type of event we want to block
        */
        template <class T>
        void blockConnection(T * _listener, enum_typeEvent _eventID)
        {
            td_mapListeners::iterator it_listener = m_mapListeners.find(_listener);

            if (it_listener != m_mapListeners.end())
            {
                td_mapConnectionBlock::iterator it_connectionBlockLow = it_listener->second.lower_bound(_eventID);
                td_mapConnectionBlock::iterator it_connectionBlockUp = it_listener->second.upper_bound(_eventID);

                for (it_connectionBlockLow; it_connectionBlockLow != it_connectionBlockUp; ++it_connectionBlockLow)
                {
                    it_connectionBlockLow->second.block();
                }
            }
        }

        /**
        * @fn       blockAllConnections(T * _listener)
        * @brief    Block all connections for a listener
        * @param    _listener      the listener of events
        */
        template <class T>
        void blockAllConnections(T * _listener)
        {
            td_mapListeners::iterator it_listener = m_mapListeners.find(_listener);

            if (it_listener != m_mapListeners.end())
            {
                for (td_mapConnectionBlock::iterator it_connectionBlock = it_listener->second.begin();
                    it_connectionBlock != it_listener->second.end();
                    ++it_connectionBlock)
                {
                    it_connectionBlock->second.block();
                }
            }
        }

        /**
        * @fn       blockAllConnections(enum_typeEvent _eventID)
        * @brief    Block all connections for an event ID
        * @param    _eventID           event we want the linked conections blocked
        */
        void blockAllConnections(enum_typeEvent _eventID)
        {
            for (td_mapListeners::iterator it_listener = m_mapListeners.begin();
                it_listener != m_mapListeners.end();
                ++it_listener)
            {
                td_mapConnectionBlock::iterator it_connectionBlockLow = it_listener->second.lower_bound(_eventID);
                td_mapConnectionBlock::iterator it_connectionBlockUp = it_listener->second.upper_bound(_eventID);

                for (it_connectionBlockLow; it_connectionBlockLow != it_connectionBlockUp; ++it_connectionBlockLow)
                {
                    it_connectionBlockLow->second.block();
                }
            }
        }

        /**
        * @fn       blockAllConnections()
        * @brief    Block all connections
        */
        void blockAllConnections()
        {
            for (td_mapListeners::iterator it_listener = m_mapListeners.begin();
                it_listener != m_mapListeners.end();
                ++it_listener)
            {
                for (td_mapConnectionBlock::iterator it_connectionBlock = it_listener->second.begin();
                    it_connectionBlock != it_listener->second.end();
                    ++it_connectionBlock)
                {
                    it_connectionBlock->second.block();
                }
            }
        }

        /**
        * @fn       unblockConnection(T * _listener, enum_typeEvent _eventID)
        * @brief    Unblock a connection for a listener and its event type
        * @param    _listener      the listener of events
        * @param    _eventID       the type of event we want to unblock
        */
        template <class T>
        void unblockConnection(T * _listener, enum_typeEvent _eventID)
        {
            td_mapListeners::iterator it_listener = m_mapListeners.find(_listener);

            if (it_listener != m_mapListeners.end())
            {
                td_mapConnectionBlock::iterator it_connectionBlockLow = it_listener->second.lower_bound(_eventID);
                td_mapConnectionBlock::iterator it_connectionBlockUp = it_listener->second.upper_bound(_eventID);

                for (it_connectionBlockLow; it_connectionBlockLow != it_connectionBlockUp; ++it_connectionBlockLow)
                {
                    it_connectionBlockLow->second.unblock();
                }
            }
        }

        /**
        * @fn       unblockAllConnections(T * _listener)
        * @brief    Unblock all connections for a listener
        * @param    _listener      the listener of events
        */
        template <class T>
        void unblockAllConnections(T * _listener)
        {
            td_mapListeners::iterator it_listener = m_mapListeners.find(_listener);

            if (it_listener != m_mapListeners.end())
            {
                for (td_mapConnectionBlock::iterator it_connectionBlock = it_listener->second.begin();
                    it_connectionBlock != it_listener->second.end();
                    ++it_connectionBlock)
                {
                    it_connectionBlock->second.unblock();
                }
            }
        }

        /**
        * @fn       unblockAllConnections(enum_typeEvent _eventID)
        * @brief    Unblock all connections for an event ID
        * @param    _eventID           event we want the linked conections blocked
        */
        void unblockAllConnections(enum_typeEvent _eventID)
        {
            for (td_mapListeners::iterator it_listener = m_mapListeners.begin();
                it_listener != m_mapListeners.end();
                ++it_listener)
            {
                td_mapConnectionBlock::iterator it_connectionBlockLow = it_listener->second.lower_bound(_eventID);
                td_mapConnectionBlock::iterator it_connectionBlockUp = it_listener->second.upper_bound(_eventID);

                for (it_connectionBlockLow; it_connectionBlockLow != it_connectionBlockUp; ++it_connectionBlockLow)
                {
                    it_connectionBlockLow->second.unblock();
                }
            }
        }

        /**
        * @fn       unblockAllConnections()
        * @brief    Unblock all connections
        */
        void unblockAllConnections()
        {
            for (td_mapListeners::iterator it_listener = m_mapListeners.begin();
                it_listener != m_mapListeners.end();
                ++it_listener)
            {
                for (td_mapConnectionBlock::iterator it_connectionBlock = it_listener->second.begin();
                    it_connectionBlock != it_listener->second.end();
                    ++it_connectionBlock)
                {
                    it_connectionBlock->second.unblock();
                }
            }
        }

        /**
        * @fn       destroyConnection(T * _listener, enum_typeEvent _eventID)
        * @brief    Block all connections for a listener
        * @param    _listener      the listener of events
        * @param    _eventID       the type of event we want to destroy
        */
        template <class T>
        void destroyConnection(T * _listener, enum_typeEvent _eventID)
        {
            td_mapListeners::iterator it_listener = m_mapListeners.find(_listener);

            if (it_listener != m_mapListeners.end())
            {
                td_mapConnectionBlock::iterator it_connectionBlockLow = it_listener->second.lower_bound(_eventID);
                td_mapConnectionBlock::iterator it_connectionBlockUp = it_listener->second.upper_bound(_eventID);

                for (it_connectionBlockLow; it_connectionBlockLow != it_connectionBlockUp; ++it_connectionBlockLow)
                {
                    it_connectionBlockLow->second.connection().disconnect();
                }

                if ( it_connectionBlockUp != it_listener->second.end() )
                    it_listener->second.erase(it_connectionBlockLow, ++it_connectionBlockUp);
                else
                    it_listener->second.erase(it_connectionBlockLow, it_connectionBlockUp);
            }
        }

        /**
        * @fn       destroyAllConnections(T * _listener)
        * @brief    Destroy all connections for a listener
        * @param    _listener      the listener of events
        */
        template <class T>
        void destroyAllConnections(T * _listener)
        {
            td_mapListeners::iterator it_listener = m_mapListeners.find(_listener);

            if (it_listener != m_mapListeners.end())
            {
                for (td_mapConnectionBlock::iterator it_connectionBlock = it_listener->second.begin();
                    it_connectionBlock != it_listener->second.end();
                    ++it_connectionBlock)
                {
                    it_connectionBlock->second.connection().disconnect();
                }

                m_mapListeners.erase(it_listener);
            }
        }

        /**
        * @fn       destroyAllConnections(enum_typeEvent _eventID)
        * @brief    Destroy all connections for an event ID
        * @param    _eventID           event we want the linked conections destroyed
        */
        void destroyAllConnections(enum_typeEvent _eventID)
        {
            for (td_mapListeners::iterator it_listener = m_mapListeners.begin();
                it_listener != m_mapListeners.end();
                ++it_listener)
            {
                td_mapConnectionBlock::iterator it_connectionBlockLow = it_listener->second.lower_bound(_eventID);
                td_mapConnectionBlock::iterator it_connectionBlockUp = it_listener->second.upper_bound(_eventID);

                for (it_connectionBlockLow; it_connectionBlockLow != it_connectionBlockUp; ++it_connectionBlockLow)
                {
                    it_connectionBlockLow->second.connection().disconnect();
                }

                if ( it_connectionBlockUp != it_listener->second.end() )
                    it_listener->second.erase(it_connectionBlockLow, ++it_connectionBlockUp);
                else
                    it_listener->second.erase(it_connectionBlockLow, it_connectionBlockUp);
            }
        }

        /**
        * @fn       destroyAllConnections()
        * @brief    Destroy all connections
        */
        void destroyAllConnections()
        {
            for (td_mapListeners::iterator it_listener = m_mapListeners.begin();
                it_listener != m_mapListeners.end();
                ++it_listener)
            {
                for (td_mapConnectionBlock::iterator it_connectionBlock = it_listener->second.begin();
                    it_connectionBlock != it_listener->second.end();
                    ++it_connectionBlock)
                {
                    it_connectionBlock->second.connection().disconnect();
                }

                it_listener->second.clear();
            }

            m_mapListeners.clear();
        }
    };

} //  namespace Event


#endif //__EVENTMANAGER_HPP__
